#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <poll.h>
#include <err.h>

#include <xcb/xcb.h>
#include <xcb/xcb_event.h>

typedef struct window {
	xcb_connection_t	*con;
	xcb_screen_t		*scr;
	xcb_window_t		 win;
} win_t;

static xcb_gc_t get_font_gc(win_t *gwin, const char *font_name)
{
	xcb_font_t font = xcb_generate_id (gwin->con);
	xcb_void_cookie_t font_cookie = xcb_open_font(gwin->con, font,
			strlen(font_name), font_name);
	/* create graphics context */
	xcb_gcontext_t  gc  = xcb_generate_id(gwin->con);
	uint32_t mask		= XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
	uint32_t values[3]	= { gwin->scr->black_pixel, gwin->scr->white_pixel, font };
	xcb_void_cookie_t gc_cookie = xcb_create_gc(gwin->con, gc, gwin->win, mask, values );
	/* close font */
	font_cookie = xcb_close_font(gwin->con, font);

	return gc;
}

void draw_text(win_t *win, int16_t x, int16_t y, const char *text)
{
	xcb_gcontext_t gc = get_font_gc(win, "fixed");

	/* draw the text */
	xcb_void_cookie_t textCookie = xcb_image_text_8(win->con,
			strlen(text),
			win->win,
			gc,
			x, y,
			text);

	/* free the gc */
	xcb_void_cookie_t gcCookie = xcb_free_gc (win->con, gc);
}

void check_cookie()
{
}

void handle_event(xcb_generic_event_t *evt)
{
	switch (XCB_EVENT_RESPONSE_TYPE(evt)) {
		case XCB_MAP_REQUEST:
			printf("map request\n");
			break;
		case XCB_DESTROY_NOTIFY:
			printf("destroy notify\n");
			break;
		case XCB_UNMAP_NOTIFY:
			printf("unmap notify\n");
			break;
		case XCB_CLIENT_MESSAGE:
			printf("client message\n");
			break;
		case XCB_CONFIGURE_REQUEST:
			printf("config request\n");
			break;
		case XCB_CONFIGURE_NOTIFY:
			printf("config notify\n");
			break;
		case XCB_PROPERTY_NOTIFY:
			printf("property notify\n");
			break;
		case XCB_ENTER_NOTIFY:
			printf("enter notify\n");
			break;
		case XCB_MOTION_NOTIFY:
			printf("motion notify\n");
			break;
		case XCB_BUTTON_PRESS:
			printf("button\n");
			break;
		case XCB_FOCUS_IN:
			printf("focus in\n");
			break;
		case XCB_MAPPING_NOTIFY:
			printf("mapping notify\n");
			break;
		case 0:
			// process_error(evt);
			printf("error\n");
			break;
		default:
			printf("default\n");
			break;
	}
}

bool check_connection(xcb_connection_t *connection)
{
	int xerr;
	if ((xerr = xcb_connection_has_error(connection)) != 0) {
		warn("The server closed the connection: ");
		switch (xerr) {
			case XCB_CONN_ERROR:
				warn("socket, pipe or stream error.\n");
				break;
			case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
				warn("unsupported extension.\n");
				break;
			case XCB_CONN_CLOSED_MEM_INSUFFICIENT:
				warn("not enough memory.\n");
				break;
			case XCB_CONN_CLOSED_REQ_LEN_EXCEED:
				warn("request length exceeded.\n");
				break;
			case XCB_CONN_CLOSED_PARSE_ERR:
				warn("can't parse display string.\n");
				break;
			case XCB_CONN_CLOSED_INVALID_SCREEN:
				warn("invalid screen.\n");
				break;
			case XCB_CONN_CLOSED_FDPASSING_FAILED:
				warn("failed to pass FD.\n");
				break;
			default:
				warn("unknown error.\n");
				break;
		}
		return false;
	} else {
		return true;
	}
}

int main()
{
	int default_screen;
	win_t window;
	win_t *win = &window;

	win->con = xcb_connect(NULL, &default_screen);

	//xcb_connection_has_error();
	if(!check_connection(win->con)) {
		exit(EXIT_FAILURE);
	}
	// get screen
	win->scr = xcb_setup_roots_iterator(xcb_get_setup(win->con)).data;
	//  ask for a graphic context ID
	win->win = xcb_generate_id (win->con);

	uint32_t mask = XCB_CW_BACK_PIXEL		 | \
					XCB_CW_BORDER_PIXEL		 |
					XCB_CW_OVERRIDE_REDIRECT | 
					XCB_CW_EVENT_MASK;

	// values[0] = screen->white_pixel;
	uint32_t value_list[] = {
		0xFFFFFF,
		0x0088FF,
		false, // no window
		XCB_EVENT_MASK_EXPOSURE |
			XCB_EVENT_MASK_BUTTON_PRESS |
			XCB_EVENT_MASK_BUTTON_RELEASE
	};

	/* connection depth, window Id, parent window,
	   x, y, width, height, border_width, class, visual, masks*/
	xcb_create_window(win->con, XCB_COPY_FROM_PARENT, win->win, win->scr->root,
			100, 100, 300, 100, 1,
			XCB_WINDOW_CLASS_INPUT_OUTPUT, win->scr->root_visual, mask, value_list);

	xcb_map_window(win->con, win->win);


	int connection_fd = xcb_get_file_descriptor(win->con);
	bool running = true;
	struct pollfd fds[1];
	fds[0].fd = connection_fd;
	fds[0].events = POLLIN;

	xcb_generic_event_t *event;

	while(running) {
		xcb_flush(win->con);

		// TODO: polling ...
		if(poll(fds, 1, -1) > 0) {
			if(fds[0].revents & POLLIN) {
				while ((event = xcb_poll_for_event(win->con)) != NULL) {
					draw_text(win, 5, 20, "helloworld");
					handle_event(event);
					// free(event);
				}
			}
		}

		if (!check_connection(win->con)) {
			running = false;
		}
	}
	// TODO: cleanup();
}

// NOTE: xcb_poly_fill_rectangle
#if 0
values[0] = 200;
values[1] = 200;
xcb_configure_window (connection, window, \
		XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
#endif
